<template>
  <div class="codeBox">
    <div class="toast" v-show="toastVal">{{ toastVal }}</div>

    <div class="controlBar">
      <el-switch
        v-model="highlight"
        width="60"
        inline-prompt
        style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
        active-text="高亮"
        inactive-text="哑光"
      />
      <div style="width: 15px"></div>

      <el-switch
        v-model="themeType"
        width="60"
        inline-prompt
        style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
        active-text="白天"
        inactive-text="黑夜"
        @change="themeChange"
      />

      <div class="copy" @click="copyText">复制</div>
    </div>

    <div class="codeContainer">
      <div class="lineNumber">
        <div v-for="item in linesArray.length + 2" :key="item">{{ item }}</div>
      </div>

      <div id="codeDisplay" ref="codeDisplay"></div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue'

import hljs from 'highlight.js/lib/core'
import javascript from 'highlight.js/lib/languages/javascript'
hljs.registerLanguage('javascript', javascript)

// import 'highlight.js/styles/an-old-hope.css'
// import 'highlight.js/styles/atom-one-light.css'

const props = defineProps({
  jsStr: { type: String, readonly: true, required: true },
  border: { type: Boolean, readonly: true, default: true },
})

onMounted(() => {
  beautifyJs(props.jsStr)
  setBorder()
})

const themeBorderWidth = ref<'1px' | 0>('1px')
const setBorder = () => {
  if (props.border) themeBorderWidth.value = '1px'
  else themeBorderWidth.value = 0
}

// 代码是否高亮
const highlight = ref<boolean>(true)
// 每一行代码数组
const linesArray = ref<string[]>([])
// 每一行代码前需要添加的缩进空格
const linesSpace = ref<number[]>([])
// 代码展示的容器
const codeDisplay = ref<HTMLElement>(null)

watch(
  () => props.jsStr,
  () => beautifyJs(props.jsStr)
)
watch(
  () => highlight.value,
  () => createDom(linesArray.value)
)

//
// 美化代码
const beautifyJs = (jsCode: string) => {
  let lines = jsCode.split('\n')

  for (let i = lines.length - 1; i >= 0; i--) {
    if (lines[i].trim().length === 0) lines.length = i
    else break
  }

  for (let i = 0; i < lines.length; i++) {
    if (lines[i].trim().length !== 0) {
      const temp = []
      for (let j = i; j < lines.length; j++) temp.push(lines[j])
      lines = [...temp]
      break
    }
  }

  let indentLevel = 0
  const beautifiedLines = []

  lines.forEach((line) => {
    let trimmedLine = line.trim()

    if (trimmedLine.startsWith('}') || trimmedLine.startsWith('});')) {
      indentLevel--
    }

    beautifiedLines.push(' '.repeat(indentLevel) + trimmedLine)

    if (trimmedLine.endsWith('{') || trimmedLine.endsWith('({')) {
      indentLevel++
    }
  })

  const s = beautifiedLines.join('\n')

  getLineSpace(s)
}

//
//
// 获取每行代码缩进
const getLineSpace = (str: string) => {
  linesSpace.value = []
  const arr = str.split('\n')

  for (let i = 0; i < arr.length; i++) {
    linesSpace.value[i] = arr[i].length - arr[i].trimStart().length
  }

  linesArray.value = arr
  createDom(arr)
}

//
//
// 生成代码块
const createDom = (elementArray: string[]) => {
  let lineNumber = 0
  const codeBox = codeDisplay.value
  codeBox.innerHTML = ''

  elementArray.forEach((item) => {
    const parentNode = addTag(item.trim(), lineNumber)
    codeBox?.appendChild(parentNode)
    lineNumber++
  })

  // 添加一个 script 标签包裹住js代码块
  const start = document.createElement('div')
  const span1 = document.createElement('span')
  const span2 = document.createElement('span')
  const span3 = document.createElement('span')

  span1.innerText = '<script'
  span1.style.paddingRight = '10px'
  span2.innerText = 'lang="ts"'
  span2.style.paddingRight = '10px'
  span3.innerText = 'setup>'
  start.style.color = '#777777'

  start.appendChild(span1)
  start.appendChild(span2)
  start.appendChild(span3)
  codeBox.insertBefore(start, Array.from(codeBox.children)[0])

  const end = document.createElement('div')
  end.innerText = '</scr' + 'ipt>'
  end.style.color = '#777777'
  codeBox?.appendChild(end)

  //
  // 生成标签
  function addTag(element: string, line: number) {
    const parentNode = document.createElement('div')
    parentNode.style.display = 'flex'
    parentNode.style.alignItems = 'center'
    parentNode.style.height = '22px'

    element.split(' ').forEach((item) => {
      const span = document.createElement('span')
      span.innerText = item
      span.style.paddingRight = '10px'

      // 代码高亮
      if (highlight.value) hljs.highlightElement(span)
      parentNode.appendChild(span)
    })

    //
    // 格式化代码，
    for (let i = 0; i < linesSpace.value[line]; i++) {
      let span = document.createElement('div')

      span.style.height = '22px'
      span.style.width = '0px'
      span.style.borderRight = '1px solid #cccccc'
      span.style.marginLeft = '6px'
      span.style.marginRight = '18px'
      span.style.maxWidth = '3px'
      span.style.backgroundColor = 'rgba(0,0,0,0)'

      parentNode.insertBefore(span, Array.from(parentNode.children)[0])
    }

    return parentNode
  }
}

onMounted(() => {
  loadTheme('atom-one-light')
  themeChange(true)
})

//
// 主题类型
const themeType = ref<boolean>(true)
// 主题颜色
const themeColor = ref<string>('')
const themeBorder = ref<string>('')
const themeButton = ref<string>('')
// 主题样式链接
const themeLink = ref<HTMLLinkElement>(null)

// 主题改变
const themeChange = (val: boolean) => {
  if (val) {
    themeColor.value = '#f8f5f5'
    themeBorder.value = '#dbc3b3'
    themeButton.value = '#777777'
    loadTheme('atom-one-light')
  } else {
    themeColor.value = '#1C1D21'
    themeBorder.value = '#1C1D21'
    themeButton.value = '#C0C4CC'
    loadTheme('an-old-hope')
  }
}

// 加载主题
const loadTheme = async (theme) => {
  if (themeLink.value) document.head.removeChild(themeLink.value)

  let link = document.createElement('link')
  link.rel = 'stylesheet'
  link.id = 'theme-link'
  link.href = new URL(`./${theme}.css`, import.meta.url).href

  document.head.appendChild(link)
  themeLink.value = link

  // if (theme === 'an-old-hope') {
  //   link.href =
  //     'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/styles/an-old-hope.min.css'
  // }
}

//
//
// 复制文本
const toastVal = ref<string>('')
const timer = ref<any>(null)

const copyText = () => {
  navigator.clipboard
    .writeText(props.jsStr)
    .then(() => {
      toastVal.value = '复制成功'
      clearTimeout(timer.value)
      timer.value = setTimeout(() => (toastVal.value = ''), 2000)
    })
    .catch(() => {
      toastVal.value = '复制失败'
      clearTimeout(timer.value)
      timer.value = setTimeout(() => (toastVal.value = ''), 2000)
    })
}
</script>

<style lang="less" scoped>
.codeBox {
  width: 100%;
  position: relative;
  background-color: v-bind(themeColor);
  border: v-bind(themeBorderWidth) solid v-bind(themeBorder);
  border-radius: 8px;
  transition: all 0.5s;

  > .toast {
    position: absolute;
    top: 20px;
    left: 50%;
    transform: translate(-50%, 0);
    padding: 6px 15px;
    border-radius: 3px;
    background-color: #00000099;
    color: white;
    letter-spacing: 1px;
    user-select: none;
  }

  > .controlBar {
    padding: 0 15px;
    width: calc(100% - 30px);
    display: flex;
    justify-content: flex-end;
    align-items: center;

    > .copy {
      margin-left: 16px;
      font-weight: bold;
      font-size: 14px;
      color: v-bind(themeButton);
      cursor: pointer;
      user-select: none;
    }
  }

  --lineHeight: 22px;

  > .codeContainer {
    display: flex;
    width: 100%;
    overflow: auto;

    > .lineNumber {
      padding: 0px 16px 10px 10px;
      user-select: none;

      > div {
        line-height: var(--lineHeight);
        letter-spacing: 1px;
        font-size: 14px;
        text-align: end;
        font-weight: bold;
        color: #777777;
        font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
          Microsoft YaHei, SimSun, sans-serif;
      }
    }

    > #codeDisplay {
      line-height: var(--lineHeight);
      font-size: 15px;
      font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
        Microsoft YaHei, SimSun, sans-serif;
      font-weight: bold;
      color: v-bind(themeButton);
    }
  }
}
</style>
